import cv2
import numpy as np
import time
import matplotlib.pyplot as plt
import scipy
import os
import math


def main():
    """
    测试图像查找拐点的主函数
    :return: void
    """

    # a = np.arange(9).reshape(3, 3)
    # r, c = np.shape(a)
    # print('原始数组：')
    # for y in range(r):
    #     for x in range(c):
    #         print(a[y][x])

    # a = np.array([2, 3, 0])
    # b = np.array([1, 2, 0])
    #
    # print(np.cross(a, b))
    #
    # print(np.linalg.norm(np.cross(a, b)))
    #
    # c = np.zeros([1, 3], dtype=float, order='C')
    # print("赋值前: ", c)
    # for i in range(len(c[0])):
    #     c[0][i] = i
    #
    # print("赋值后: ", c)

    myClass = DetClass("D:/Data/WoKe/2021-03-12/1/")
    myClass.get_file_list(True)

    print("=================")
    # myClass.print_file_name_list()
    myClass.run()


class DetClass:
    """
    拐点检测类
    """

    # 定义基本属性
    filePath = ''  # 测试图片的文件路径
    fileList = []
    fileNameList = []

    grayImg = []
    binImg = []

    objectContour = []
    right_border_pts = ()
    left_border_pts = ()
    # 定义私有属性
    __size = 0
    __ImgH = 0
    __ImgW = 0

    # 定义构造函数
    def __init__(self, filePath):
        self.filePath = filePath
        # 将列表清空
        self.fileList.clear()
        self.fileNameList.clear()
        return

    def get_file_list(self, bSort=True):
        """
        根据输入的文件路径得到文件列表名
        :param bSort: 是否对文件名进行排序,只对文件名是数字的
        :return: void
        """
        self.fileList = os.listdir(self.filePath)
        numList = []
        # 遍历整个文件列表
        for fileName in self.fileList:
            if fileName.endswith(".bmp"):
                # print("图像名: ", fileName)
                if bSort:
                    numName = fileName[:-4]
                    numList.append(int(numName))
                    continue
                else:
                    self.fileNameList.append(self.filePath + fileName)
                    return

        # 对文件名进行排序
        numList.sort()
        for num in numList:
            self.fileNameList.append(self.filePath + str(num) + ".bmp")
        # 打印信息
        # print("文件列表size: ", len(self.fileNameList))

    def print_file_name_list(self):
        """
        将得到文件名进行打印
        :return:
        """
        for name in self.fileNameList:
            print(name)

    def get_target_contour(self, grayImg, binThresh):
        """
        得到目标轮廓，
        :return: 轮廓的信息
        """
        # 对灰度图像进行二值化处理
        ret, self.binImg = cv2.threshold(grayImg, binThresh, 255, cv2.THRESH_BINARY)
        ImgH, ImgW = self.binImg.shape

        self.__ImgH = ImgH
        self.__ImgW = ImgW
        self.__size = ImgH * ImgW

        # 定义ROI区域
        roiRect = [100, 10, 1100, 200]  # x, y, w, h
        roiImg = self.binImg[10:200, 100:1100]  # 选取ROI区域row[10:200],col[100:1100]
        # 检测ROI图像的轮廓
        ROI_contours, ROI_hierarchy = cv2.findContours(roiImg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

        if len(ROI_contours) < 1:
            return []

        # -----------在ROI区域中检查出最大轮廓的中心点-----------
        maxContourID = 0
        maxContourSize = 0

        # 遍历所有轮廓，找到最大轮廓Id
        for i in range(len(ROI_contours)):
            if len(ROI_contours[i]) > maxContourSize:
                maxContourSize = len(ROI_contours[i])
                maxContourID = i

        # 得到最大轮廓
        maxContour = ROI_contours[maxContourID]

        # 最大轮廓的矩并得到重心点坐标(重心坐标一定要在轮廓内部)
        M = cv2.moments(maxContour)
        centerX = int(M['m10'] / M['m00'])
        centerY = int(M['m01'] / M['m00'])

        # x = 0
        if cv2.pointPolygonTest(maxContour, (centerX, centerY), False) <= 0:
            print("利用矩计算的重心不在轮廓内 ", centerX, centerY)
            for i in range(1100):
                if cv2.pointPolygonTest(maxContour, (i, centerY), False) == 1:
                    # print(i, " ---", cv2.pointPolygonTest(maxContour, (i, centerY), False))
                    centerX = i
                    break

            # 将以上坐标转换到原图像坐标下
            centerX += 100
            centerY += 10
        else:
            # 将以上坐标转换到原图像坐标下
            centerX += 100
            centerY += 10

        binImgCopy = self.binImg.copy()
        # -----------在原图像中检测轮廓，轮廓中包含centerPt的就是目标轮廓-----------
        contours, hierarchy = cv2.findContours(binImgCopy, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
        # 遍历所有轮廓，找到包含centerX、centerY坐标的轮廓
        for i in range(len(contours)):
            if cv2.pointPolygonTest(contours[i], (centerX, centerY), False) >= 0:
                print("找到目标轮廓")
                return contours[i]

    def get_right_border_points(self):
        """
        得到轮廓的右边界点集
        :return 右边界的轮廓点
        """
        points = []
        # -------得到目标轮廓的外界矩形-------
        x, y, w, h = cv2.boundingRect(self.objectContour)
        for r in range(h + y, y, -1):
            for c in range(w + x, x, -1):
                if self.binImg[r][c] > 0:
                    # print("边缘点坐标:", c, r)
                    points.append([c, r])
                    break
        print("points Size: ", len(points))
        return points

    def get_left_border_points(self, ):
        """
        得到轮廓的左边界点集
        :return 左边界的轮廓点
        """
        points = []
        # -------得到目标轮廓的外界矩形-------
        x, y, w, h = cv2.boundingRect(self.objectContour)
        for r in range(y, h + y, 1):
            for c in range(x, w + x, 1):
                if self.binImg[r][c] > 0:
                    # print("边缘点坐标:", c, r)
                    points.append([c, r])
                    break
        print("points Size: ", len(points))
        return points

    def fit_line_params(self):
        """
        根据右边界点拟合直线
        :return 拟合的直线参数和图像上下边界的交点
        """
        upPt = ()  # np.zeros([1, 2], dtype=float, order='C')
        downPt = ()  # np.zeros([1, 2], dtype=float, order='C')

        line = cv2.fitLine(np.array(self.right_border_pts), cv2.DIST_WELSCH, 0, 0.01, 0.01)
        # line = cv2.fitLine(np.array(self.left_border_pts), cv2.DIST_WELSCH, 0, 0.01, 0.01)

        # 拟合的直线数垂直X轴的
        if line[0] < 0.008726:
            pt1 = (int(line[2]), 0)
            pt2 = (int(line[2]), int(self.__ImgH))

            upPt = pt1
            downPt = pt2
        else:
            K = line[1] / line[0]
            B = line[3] - (K * line[2])

            pt1 = (int((0.0 - B) / K), 0)
            pt2 = (int((self.__ImgH - B) / K), int(self.__ImgH))

            upPt = pt1
            downPt = pt2

        return line, upPt, downPt

    def pointToLineDist(self, point, line):
        """
        点到直线的距离
        :param point 直线外的一点 [x, y]
        :param line  直线方程参数数组 [cos,sin,x0,y0]
        :return float 距离
        """
        h = self.__ImgH

        x2 = point[0] * 1.0 - line[2]
        y2 = point[1] * 1.0 - line[3]
        z2 = 0.0
        x1 = line[0]
        y1 = line[1]
        z1 = 0.0
        # c = np.zeros([1, 3], dtype=float, order='C')
        vec1 = np.zeros([1, 3], dtype=float, order='C')
        vec2 = np.zeros([1, 3], dtype=float, order='C')
        vec1[0][0] = x1
        vec1[0][1] = y1
        vec1[0][2] = z1
        vec2[0][0] = x2
        vec2[0][1] = y2
        vec2[0][2] = z2
        dist = np.linalg.norm(np.cross(vec1, vec2))

        # print("dist : ", dist)
        return dist

    def calculate_triangle_area(self, point1, point2, point3):
        """
        计算三角形的面积
        :param point1:  三角形的顶点1
        :param point2:  三角形的顶点2
        :param point3:  三角形的顶点3
        :return: 三角形面积
        """

        imgH = self.__ImgH

        # 利用三角形面积计算公式计算面积
        aX_bY = point1[0] * point2[1]
        bX_cY = point2[0] * point3[1]
        cX_aY = point3[0] * point1[1]
        aX_cY = point1[0] * point3[1]
        bX_aY = point2[0] * point1[1]
        cX_bY = point3[0] * point2[1]

        area = (aX_bY + bX_cY + cX_aY - aX_cY - bX_aY - cX_bY) / 2.0

        # print("area : ", area)

        return abs(area)

    def run(self):
        """
        检测类的执行函数
        :return:
        """
        WinName1 = "Win_1"
        WinName2 = "Win_2"
        for name in self.fileNameList:
            print("正在处理图像: ", name)
            # 创建两个窗口
            cv2.namedWindow(WinName1, 0)
            cv2.namedWindow(WinName2, 0)
            # 读出图像
            # self.grayImg = cv2.imread("D:/Data/WoKe/2021-03-22/2/226.bmp", cv2.IMREAD_GRAYSCALE)
            self.grayImg = cv2.imread(name, cv2.IMREAD_GRAYSCALE)
            # 转换为灰度图像
            imgClr = cv2.cvtColor(self.grayImg, cv2.COLOR_GRAY2BGR)
            # 得到目标轮廓
            self.objectContour = self.get_target_contour(self.grayImg, 120)
            if len(self.objectContour) < 10:
                print("objectContour size:", len(self.objectContour))
                continue
            # 查看目标轮廓的点个数
            # print("objectContour size:", len(self.objectContour))
            # 画出目标轮廓
            # drawImg = cv2.drawContours(imgClr, self.objectContour, -1, (0, 255, 0), 2)
            # 检测目标轮廓的右边界点
            self.right_border_pts = self.get_right_border_points()
            # self.left_border_pts = self.get_left_border_points()
            # 在图像上画出右边界点
            # for i in range(len(self.right_border_pts)):
            #     cv2.circle(imgClr, tuple(self.right_border_pts[i]), 1, (0, 0, 255), 2)
            # 拟合直线并得到直线与图像上下边界的交点
            line, pt1, pt2 = self.fit_line_params()
            # print("len(pt1) ", len(pt1))
            # print("len(pt2) ", len(pt2))
            print("拟合的直线参数", line)
            # print("两个边缘点", tuple(pt1))
            # 当点是list类型时，使用如下方式
            # cv2.circle(imgClr, tuple(pt1[0]), 2, (0, 0, 255), 2)
            # cv2.circle(imgClr, tuple(pt2[0]), 2, (0, 0, 255), 2)
            cv2.line(imgClr, pt1, pt2, (0, 255, 255), 2)
            # 从边界点中检测离拟合直线最远的点
            maxDistId = 0
            maxDist = 0
            maxDistPt = []

            minYPt = []
            minY = self.__ImgH
            for i in range(len(self.right_border_pts)):
                # 检测右边界点Y坐标最小的点
                ptY = self.right_border_pts[i][1]
                if ptY < minY:
                    minY = ptY
                    minYPt = self.right_border_pts[i]
                # 计算点到直线的距离
                dist = self.pointToLineDist(self.right_border_pts[i], line)
                if dist > maxDist:
                    maxDist = dist
                    maxDistId = i
                    maxDistPt = self.right_border_pts[i]
            # 画出检测出来的最远的点
            cv2.circle(imgClr, tuple(self.right_border_pts[maxDistId]), 1, (0, 0, 255), 2)

            cv2.circle(imgClr, tuple(minYPt), 1, (0, 255, 0), 2)

            maxAreaId_1 = 0
            maxArea_1 = 0
            maxAreaPt_1 = []
            # 计算到最远点和最上边点直线最远的点
            for i in range(len(self.right_border_pts)):
                # 计算三角形的面积
                area = self.calculate_triangle_area(self.right_border_pts[i], maxDistPt, minYPt)
                if area > maxArea_1:
                    maxArea_1 = area
                    maxAreaId_1 = i
                    maxAreaPt_1 = self.right_border_pts[i]

            cv2.circle(imgClr, tuple(self.right_border_pts[maxAreaId_1]), 1, (0, 0, 255), 2)

            # roiImg = self.grayImg[20:200, 100:1100]
            # 显示原图像和处理结果
            cv2.imshow(WinName1, self.grayImg)
            cv2.imshow(WinName2, imgClr)
            cv2.waitKey(200)
        cv2.destroyAllWindows()
        return


# 主函数入口
if __name__ == '__main__':
    main()
